import datetime
import pickle

import numpy as np
import pandas as pd
from tensorflow import keras

from src.laptop.model import laptop_model_config
from src.laptop.preprocessor.model_data_preprocessor import process_predict_data
from src.utils.config import logger
from src.utils.db_processor import mysql_prediction_processor
from src.utils.util import process_model_file_date_mapping

# 过期天数
EXPIRED_DAYS = 120


class SKULevelHistoryPriceTask:
    def __init__(self):
        now = datetime.datetime.now()
        self.expire_time = now - datetime.timedelta(days=EXPIRED_DAYS)

        self.task_list = None
        self.expire_task_list = None
        self.task_results = None

    def get_current_task(self):
        """
        获取当前任务列表
        :return:
        """
        sql = """
        SELECT id as task_id, sku_id, product_level_name 
        FROM laptop_sku_level_task 
        WHERE task_status <> '任务已完成' AND task_type = '历史价格'"""
        self.task_list = mysql_prediction_processor.load_sql(sql)

    def process_current_task(self):
        """
        处理当前任务
        :return:
        """
        if self.task_list.empty:
            logger.info('task is empty, SKIP!')
            return

        self.__load_model_mapping()
        if not self.models_mapping:
            logger.error('No model exist!')
            return

        logger.info('processing sku level history price task...')
        sku_info = self.task_list.to_dict('records')
        predict_data = process_predict_data(sku_info, process_level=False)
        predict_data['period'] = '0'

        task_results = []
        for back_date in self.models_mapping.keys():
            logger.info('processing sku level history price task date@{}'.format(back_date))
            predict_result = self.__process_predict_task(predict_data, back_date)
            if predict_result is not None:
                task_results.append(predict_result)

        self.task_results = pd.concat(task_results, ignore_index=True)

    def save_current_task(self):
        """
        保存当前任务结果
        :return:
        """
        if self.task_results is None:
            return

        if self.task_results.empty:
            logger.error('no sku level history price task results!')
            success_task_id = []
            failed_task_id = self.task_list['task_id'].tolist()
        else:
            processed_task_id = self.task_results['task_id'].drop_duplicates()
            success_task_id = self.task_list[self.task_list['task_id'].isin(processed_task_id)]['task_id'].tolist()
            failed_task_id = self.task_list[~self.task_list['task_id'].isin(processed_task_id)]['task_id'].tolist()

        update_success_sql = "UPDATE laptop_sku_level_task SET task_status = '任务已完成' WHERE id in ({})".format(
            ','.join(str(x) for x in success_task_id))
        update_failed_sql = "UPDATE laptop_sku_level_task SET task_status = '任务失败' WHERE id in ({})".format(
            ','.join(str(x) for x in failed_task_id))

        if success_task_id:
            mysql_prediction_processor.execute_sql(update_success_sql)
        if failed_task_id:
            mysql_prediction_processor.execute_sql(update_failed_sql)
        if not self.task_results.empty:
            insert_sql = "REPLACE INTO laptop_sku_level_price_trend(task_id, price, predict_date) VALUES(%s, %s, %s)"
            mysql_prediction_processor.execute_insert_sql(insert_sql,  self.task_results[
                ['task_id', 'price', 'predict_date']].to_records(index=False).tolist())

    def launch_task(self):
        """
        启动任务
        :return:
        """
        self.get_current_task()
        self.process_current_task()
        self.save_current_task()

    def __load_model_mapping(self):
        """
        读取日期与模型文件映射关系
        :return:
        """
        self.models_mapping = process_model_file_date_mapping(laptop_model_config.MODEL_DIR +
                                                              laptop_model_config.MODEL_FILE_NAME + '*')
        self.ohe_product_mapping = process_model_file_date_mapping(laptop_model_config.MODEL_DIR + 'ohe_product' +
                                                                   laptop_model_config.PREPROCESSOR_SUFFIX_NAME + '*')
        self.ohe_level_mapping = process_model_file_date_mapping(laptop_model_config.MODEL_DIR + 'ohe_level' +
                                                                 laptop_model_config.PREPROCESSOR_SUFFIX_NAME + '*')
        self.ohe_attr_mapping = process_model_file_date_mapping(laptop_model_config.MODEL_DIR + 'ohe_attr' +
                                                                laptop_model_config.PREPROCESSOR_SUFFIX_NAME + '*')
        self.ohe_period_mapping = process_model_file_date_mapping(laptop_model_config.MODEL_DIR + 'ohe_period' +
                                                                  laptop_model_config.PREPROCESSOR_SUFFIX_NAME + '*')
        self.scaler_mapping = process_model_file_date_mapping(laptop_model_config.MODEL_DIR + 'scaler' +
                                                              laptop_model_config.PREPROCESSOR_SUFFIX_NAME + '*')

    def __process_predict_task(self, predict_data, back_date):
        """
        预测回测日期价格
        :param predict_data: 预测数据
        :param back_date: 回测日期
        :return:
        """
        data = predict_data.copy()
        model_file = self.models_mapping.get(back_date)
        if not model_file:
            return None

        model = keras.models.load_model(model_file)
        with open(self.ohe_product_mapping[back_date], 'rb') as f:
            ohe_product = pickle.load(f)
        with open(self.ohe_level_mapping[back_date], 'rb') as f:
            ohe_level = pickle.load(f)
        with open(self.ohe_attr_mapping[back_date], 'rb') as f:
            ohe_attr = pickle.load(f)
        with open(self.ohe_period_mapping[back_date], 'rb') as f:
            ohe_period = pickle.load(f)
        with open(self.scaler_mapping[back_date], 'rb') as f:
            scaler = pickle.load(f)

        x_product = ohe_product.transform(data[laptop_model_config.PRODUCT_FEATURES])
        x_level = ohe_level.transform(data[laptop_model_config.LEVEL_FEATURES])
        x_attr = ohe_attr.transform(data[laptop_model_config.ATTR_FEATURES])
        x_period = ohe_period.transform(data[laptop_model_config.PERIOD_FEATURES])

        price = model.predict([x_product, x_level, x_attr, x_period])
        data['price'] = np.round(scaler.inverse_transform(price.reshape((-1, 1))).flatten()).astype(int)
        data['predict_date'] = back_date

        return data
